package cn.wellt.mqtt.config;

import org.springframework.util.StringUtils;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 消息订阅接收者
 *
 * @author caojingchen
 * @date 2021/2/1 11:33
 */
public class MqttMessageListenerHolder {

    private static Map<String, String> listeners;
    private static Map<String, Integer> qoss;

    private static TopicSubscribeTree tree;

    static {
        listeners = new ConcurrentHashMap<>();
        qoss = new ConcurrentHashMap<>();
        tree = new TopicSubscribeTree();
    }

    /**
     * 注册订阅处理
     *
     * @param topic        主题
     * @param listenerName 消息处理
     */
    public static void register(String topic, String listenerName, int qos) {
        if (!StringUtils.hasText(listenerName)) {
            return;
        }
        listeners.putIfAbsent(topic, listenerName);
        qoss.putIfAbsent(topic, qos);
        insertTree(topic, qos);
    }

    public static String getListener(String topic) {
        return listeners.get(translateSubscribeName(topic));
    }

    public static Map<String, String> getListeners() {
        return listeners;
    }

    public static Map<String, Integer> getQoss() {
        return qoss;
    }


    private static void insertTree(String topic, int qos) {
        TopicSubscribeTree cur = tree;
        String[] levels = topic.split("/");
        for (int i = 0; i < levels.length; i++) {
            String level = levels[i];
            if (null == cur.getChild()) {
                cur.setChild(new ConcurrentHashMap<>());
                TopicSubscribeTree newTree = new TopicSubscribeTree();
                newTree.setLevelName(level);
                cur.getChild().put(level, newTree);
                cur = newTree;
            } else if (cur.getChild().containsKey(level)) {
                cur = cur.getChild().get(level);
                continue;
            } else {
                TopicSubscribeTree newTree = new TopicSubscribeTree();
                newTree.setLevelName(level);
                cur.getChild().put(level, newTree);
                cur = newTree;
            }
            if (levels.length - 1 == i) {
                cur.setQos(qos);
            }
        }
    }

    /**
     * 通过实际topic地址找最匹配的订阅处理器
     *
     * @param topic 主题
     * @return 订阅处理器topic表达式
     */
    private static String translateSubscribeName(String topic) {
        StringBuilder sb = new StringBuilder();
        TopicSubscribeTree cur = tree;
        while (null != cur.child) {
            // 字符串为空，判断#+后退出
            if (!StringUtils.hasText(topic)) {
                if (cur.child.containsKey("#")) {
                    sb.append("/#");
                    return sb.toString();
                } else if (cur.child.containsKey("+")) {
                    sb.append("/+");
                    return sb.toString();
                } else {
                    return sb.toString();
                }
            }
            if (0 != sb.length()) {
                sb.append("/");
            }

            int firstIndex = topic.indexOf("/");
            String level = firstIndex >= 0 ? topic.substring(0, topic.indexOf("/")) : topic;
            String nextTopic = topic.contains("/") ? topic.substring(topic.indexOf("/") + 1) : null;
            if (cur.child.containsKey("+")) {
                sb.append("+");
                topic = nextTopic;
                cur = cur.child.get("+");
            } else if (cur.child.containsKey("#")) {
                sb.append("#");
                return sb.toString();
            } else {
                sb.append(level);
                topic = nextTopic;
                cur = cur.child.get(level);
            }
        }
        return sb.toString();
    }

    /**
     * 主题订阅前缀树
     */
    private static class TopicSubscribeTree {

        /**
         * 当前level名
         */
        private String levelName;

        /**
         * 下级level名
         */
        private Map<String, TopicSubscribeTree> child;

        /**
         * null != qos 代表有订阅
         */
        private Integer qos;

        public String getLevelName() {
            return levelName;
        }

        public void setLevelName(String levelName) {
            this.levelName = levelName;
        }

        public Map<String, TopicSubscribeTree> getChild() {
            return child;
        }

        public void setChild(Map<String, TopicSubscribeTree> child) {
            this.child = child;
        }

        public Integer getQos() {
            return qos;
        }

        public void setQos(Integer qos) {
            this.qos = qos;
        }
    }


}
